﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace Brain
{
    /// <summary>
    /// Class for Animating Sprites
    /// </summary>
    public class Animation_Sprite : IBaseAnimation
    {
        /// <summary>
        /// All the Frames this Animation class have
        /// </summary>
        public KeyFrame_Sprite[] Frames { get; set; }

        /// <summary>
        /// If the animation should Loop
        /// </summary>
        public bool Loop { get; set; }

        private int currentFrame;
        /// <summary>
        /// The Current Frame this Animation is at.
        /// Be aware that setting this value WILL Update the
        /// Animation
        /// </summary>
        public int CurrentFrame
        {
            get { return currentFrame; }
            set
            {
                currentFrame = value;
                Update();
            }
        }

        /// <summary>
        /// The Length of the Animation. Can change if the Animation is looping, due
        /// to the Loop Restart Time
        /// </summary>
        public int AnimationEnd
        {
            get
            {
                if (Loop)
                {
                    return totalTime + LoopRestartTime;
                }
                else
                {
                    return totalTime;
                }
            }
        }

        /// <summary>
        /// The Frame the Animation should start
        /// </summary>
        public int StartFrame { get; set; }

        /// <summary>
        /// The Sprite we are modifying
        /// </summary>
        public Sprite Sprite;

        /// <summary>
        /// The Time it takes for the Loop to restart
        /// </summary>
        public int LoopRestartTime = 50;

        /// <summary>
        /// Creates a new Animation_Vector2. Constructor used for Deserializing ONLY, use the another
        /// </summary>
        public Animation_Sprite()
        {
            ShouldSerialize = true;
        }
        /// <summary>
        /// Creates a new Animation_Sprite
        /// </summary>
        public Animation_Sprite(Sprite sprite)
        {
            this.Sprite = sprite;
            ShouldSerialize = true;
        }

        private List<KeyFrame_Sprite> frames;

        private int totalTime;
        public void SubmitFrame(KeyFrame_Sprite frame)
        {
            if (frames == null)
            {
                frames = new List<KeyFrame_Sprite>();
            }
            if (frame.Time > totalTime)
            {
                totalTime = frame.Time;
            }

            this.frames.Add(frame);

            isOptimized = false;
        }

        private bool isOptimized = false;
        /// <summary>
        /// If True, the Array of Frames is ordered by time,
        /// and thus the Array of Frames is made, not the list
        /// </summary>
        public bool IsOptimized
        {
            get { return isOptimized; }
        }

        public void Optimize()
        {
            this.frames.Sort(CompareKeyFrames);
            this.Frames = frames.ToArray();

            isOptimized = true;
        }
        private int CompareKeyFrames(KeyFrame_Sprite frame1, KeyFrame_Sprite frame2)
        {
            return frame1.Time.CompareTo(frame2.Time);
        }

        private void Update()
        {
            if (Frames == null || !isOptimized)
            {
                Optimize();
            }

            bool isNull;
            int index;
            KeyFrame_Sprite Frame = FindLastKeyFrame(CurrentFrame, out isNull, out index);

            if (!isNull)
            {
                bool nextNull;
                bool isRestart;
                KeyFrame_Sprite Next = FindNextFrame(index, out nextNull, out isRestart);

                if (nextNull)
                {
                    // Nothing to Interpolate, just set the Position to the Frame pos                    
                    if (Frame.BoundsRectangle != null)
                    {
                        Sprite.BoundsRectangle = Frame.BoundsRectangle.Value;
                    }
                    if (Frame.BoundsRectangleColor != null)
                    {
                        Sprite.BoundsRectangleColor = Frame.BoundsRectangleColor.Value;
                    }
                    if (Frame.Color != null)
                    {
                        Sprite.Color = Frame.Color.Value;
                    }
                    if (Frame.Position != null)
                    {
                        Sprite.Position = Frame.Position.Value;
                    }
                    if (Frame.RotationAngle != null)
                    {
                        Sprite.RotationAngle = Frame.RotationAngle.Value;
                    }
                    if (Frame.Shadow != null)
                    {
                        Sprite.Shadow = Frame.Shadow.Value;
                    }
                    if (Frame.SourceRectangle != null)
                    {
                        Sprite.SourceRectangle = Frame.SourceRectangle.Value;
                    }
                    if (Frame.Text != null)
                    {
                        Sprite.Text = Frame.Text;
                    }
                }
                else
                {
                    if (isRestart)
                    {
                        float amount = ((float)CurrentFrame - Frame.Time) / (float)LoopRestartTime;
                        LerpSprite(amount, Frame, Next);
                    }
                    else
                    {
                        float amount = ((float)CurrentFrame - Next.Time) / ((float)Frame.Time - Next.Time);
                        LerpSprite(amount, Next, Frame);
                    }
                }
            }
        }

        protected void LerpSprite(float amount, KeyFrame_Sprite last, KeyFrame_Sprite next)
        {
            if (last.BoundsRectangle != null &&
                next.BoundsRectangle != null)
            {
                Rectangle lastValue = last.BoundsRectangle.Value;
                Rectangle nextValue = next.BoundsRectangle.Value;
                Sprite.BoundsRectangle = RectangleUtil.Lerp(lastValue, nextValue, amount);
            }
            if (last.BoundsRectangleColor != null &&
                next.BoundsRectangleColor != null)
            {
                Color lastValue = last.BoundsRectangleColor.Value;
                Color nextValue = next.BoundsRectangleColor.Value;
                Sprite.BoundsRectangleColor = Color.Lerp(lastValue, nextValue, amount);
            }
            if (last.Color != null &&
                next.Color != null)
            {
                Color lastValue = last.Color.Value;
                Color nextValue = next.Color.Value;
                Sprite.Color = Color.Lerp(lastValue, nextValue, amount);
            }
            if (last.Position != null &&
                next.Position != null)
            {
                Vector2 lastValue = last.Position.Value;
                Vector2 nextValue = next.Position.Value;
                Sprite.Position = Vector2Util.Lerp(lastValue, nextValue, amount);
            }
            if (last.RotationAngle != null &&
                next.RotationAngle != null)
            {
                float lastValue = last.RotationAngle.Value;
                float nextValue = next.RotationAngle.Value;
                Sprite.RotationAngle = FloatUtil.Lerp(lastValue, nextValue, amount);
            }
            if (last.Shadow != null &&
                next.Shadow != null)
            {
                int lastValue = last.Shadow.Value;
                int nextValue = next.Shadow.Value;
                Sprite.Shadow = (int)FloatUtil.Lerp(lastValue, nextValue, amount);
            }
            if (last.SourceRectangle != null &&
                next.SourceRectangle != null)
            {
                Rectangle lastValue = last.SourceRectangle.Value;
                Rectangle nextValue = next.SourceRectangle.Value;
                Sprite.SourceRectangle = RectangleUtil.Lerp(lastValue, nextValue, amount);
            }
            if (last.Text != null &&
                next.Text != null)
            {
                Sprite.Text = next.Text;
            }
        }

        private float currentLoopFrame;
        public void UpdateLoop(float frameDif)
        {
            currentLoopFrame += frameDif;

            while (currentLoopFrame > this.AnimationEnd)
            {
                currentLoopFrame -= this.AnimationEnd;
            }

            int current = (int)currentLoopFrame;
            if (currentFrame != current)
            {
                this.CurrentFrame = current;
            }
        }
        public void Update(int frame)
        {
            this.CurrentFrame = frame;
        }

        protected KeyFrame_Sprite FindLastKeyFrame(int nextTime, out bool isNull, out int index)
        {
            object lastFoundFrame = null;
            KeyFrame_Sprite lastFoundF = default(KeyFrame_Sprite);

            isNull = true;
            index = -1;

            for (int i = 0; i < Frames.Length; i++)
            {
                KeyFrame_Sprite fr = Frames[i];
                if (lastFoundFrame == null)
                {
                    if (fr.Time < nextTime)
                    {
                        index = i;
                        lastFoundF = fr;
                        lastFoundFrame = lastFoundF;

                        isNull = false;
                    }
                }
                else
                {
                    if (fr.Time <= nextTime &&
                        fr.Time > lastFoundF.Time)
                    {
                        index = i;
                        lastFoundF = fr;
                        lastFoundFrame = fr;
                    }
                }
            }


            return lastFoundF;
        }
        protected KeyFrame_Sprite FindNextFrame(int index, out bool isNull, out bool isRestart)
        {
            int nextIndex = index + 1;
            if (this.Frames.Length > nextIndex)
            {
                isNull = false;
                isRestart = false;
                return this.Frames[nextIndex];
            }

            if (Loop)
            {
                // Pick the first frame
                isNull = false;
                isRestart = true;
                return this.Frames[0];
            }

            isNull = true;
            isRestart = false;
            return default(KeyFrame_Sprite);
        }

        public bool ShouldSerialize { get; set; }

        public void SerializeData(SerializationData parentData, string add)
        {
            if (Frames == null | !isOptimized)
            {
                Optimize();
            }

            parentData.AddData("Anim_Sprite.Frames", this.Frames.Length, add);
            for (int i = 0; i < this.Frames.Length; i++)
            {
                KeyFrame_Sprite Frame = this.Frames[i];
                parentData.AddData("Anim_Sprite.FrameTime" + i, Frame.Time, add);
                if (Frame.BoundsRectangle != null)
                {
                    parentData.AddData("Anim_Sprite.FrameBoundsRectangle" + i, Frame.BoundsRectangle.Value, add);
                }
                if (Frame.BoundsRectangleColor != null)
                {
                    parentData.AddData("Anim_Sprite.FrameBoundsRectangleColor" + i, Frame.BoundsRectangleColor.Value, add);
                }
                if (Frame.Color != null)
                {
                    parentData.AddData("Anim_Sprite.FrameColor" + i, Frame.Color.Value, add);
                }
                if (Frame.Position != null)
                {
                    parentData.AddData("Anim_Sprite.FramePosition" + i, Frame.Position.Value, add);
                }
                if (Frame.RotationAngle != null)
                {
                    parentData.AddData("Anim_Sprite.FrameRotationAngle" + i, Frame.RotationAngle.Value, add);
                }
                if (Frame.Shadow != null)
                {
                    parentData.AddData("Anim_Sprite.FrameShadow" + i, Frame.Shadow.Value, add);
                }
                if (Frame.SourceRectangle != null)
                {
                    parentData.AddData("Anim_Sprite.FrameSourceRectangle" + i, Frame.SourceRectangle.Value, add);
                }
                if (Frame.Text != null)
                {
                    parentData.AddData("Anim_Sprite.FrameText" + i, Frame.Text, add);
                }
            }
        }

        public void DeserializeData(SerializationData parentData, string add)
        {
            int frames = parentData.GetData<int>("Anim_Sprite.Frames", add);
            for (int i = 0; i < frames; i++)
            {
                int time = parentData.GetData<int>("Anim_Sprite.FrameTime" + i, add);

                Rectangle? BoundsRectangle = parentData.GetData<Rectangle?>("Anim_Sprite.FrameBoundsRectangle" + i, add);
                Color? BoundsRectangleColor = parentData.GetData<Color?>("Anim_Sprite.FrameBoundsRectangleColor" + i, add);
                Color? Color = parentData.GetData<Color?>("Anim_Sprite.FrameColor" + i, add);
                Vector2? Position = parentData.GetData<Vector2?>("Anim_Sprite.FramePosition" + i, add);
                float? RotationAngle = parentData.GetData<float?>("Anim_Sprite.FrameRotationAngle" + i, add);
                int? Shadow = parentData.GetData<int?>("Anim_Sprite.FrameShadow" + i, add);
                Rectangle? SourceRectangle = parentData.GetData<Rectangle?>("Anim_Sprite.FrameSourceRectangle" + i, add);
                string Text = parentData.GetData<string>("Anim_Sprite.FrameText" + i, add);

                KeyFrame_Sprite frame = new KeyFrame_Sprite();
                frame.BoundsRectangle = BoundsRectangle;
                frame.BoundsRectangleColor = BoundsRectangleColor;
                frame.Color = Color;
                frame.Position = Position;
                frame.RotationAngle = RotationAngle;
                frame.Shadow = Shadow;
                frame.SourceRectangle = SourceRectangle;
                frame.Text = Text;
                frame.Time = time;

                SubmitFrame(frame);
            }

            Optimize();
        }


        public void DeserializedAll()
        {
        }
    }
}
